package Counter;

import java.text.DecimalFormat;
import java.util.Vector;

public abstract class Histogram {
	// Values below this value increment the "first" interval
	protected double lowerBound;
	// Values greater than this value increment the "last" interval
	protected double upperBound;
	// Interval / bin size
	protected double delta;
	// Number of bins
	protected int numIntervals;
	// Vector that stores the histogram data
	protected Vector<Long> bins;
	// Name of the variable
	private String observedVariable;
	// Continuous/discrete
	private valueType type;

	// Make output human readable
	protected DecimalFormat format3 = new DecimalFormat("0.000");
	protected DecimalFormat format1 = new DecimalFormat("0.0");

	public Histogram(double ub, double lb, int numIntervals,
			String observedVariable) {
		upperBound = ub;
		lowerBound = lb;
		this.observedVariable = observedVariable;
		setNumBins(numIntervals);
	}

	public void setNumBins(int amountBins) {
		this.numIntervals = amountBins;
		bins = new Vector<Long>(amountBins);
		for (int i = 0; i < amountBins; i++) {
			bins.add(new Long(0));
		}
		updateDelta();
	}

	public void setBoundaries(double ub, double lb) {
		upperBound = ub;
		lowerBound = lb;
		updateDelta();
	}

	private void updateDelta() {
		delta = (upperBound - lowerBound) / numIntervals;
	}

	// Print statistics on console
	public void report() {

		System.out.println("Observed Variable: " + observedVariable);
		System.out.println("Number of Intervals: " + numIntervals);
		System.out.println("Type: " + type);
		System.out.println("Lower bound: " + lowerBound);
		System.out.println("Upper bound: " + upperBound);
		System.out.println("Delta: " + delta);

		int totalNumberOfValues = getTotalNumberOfValues();
		double pdf; // probability density function
		double cdf = 0; // cumulative distribution function
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < bins.size(); i++) {
			sb.append("Bin: ").append(i);
			sb.append(" from: ").append(format1.format(lowerBound + i * delta));
			sb.append(" to: ").append(
					format1.format(lowerBound + (i + 1) * delta));
			sb.append(" n: ").append(bins.get(i));
			pdf = (double) bins.get(i) / totalNumberOfValues;
			cdf += pdf;
			sb.append(" PD: " + format3.format(pdf));
			sb.append(" PDC: " + format3.format(cdf)).append("\n");
		}
		System.out.println(sb);

	}

	public enum valueType {
		contineous, discrete;
	}

	public void setUpperBound(double upperBound) {
		this.upperBound = upperBound;
	}

	public void setLowerBound(double lowerBound) {
		this.lowerBound = lowerBound;
	}

	protected void setType(valueType type) {
		this.type = type;
	}

	int getTotalNumberOfValues() {
		int totalNumberOfValues = 0;
		for (int i = 0; i < bins.size(); i++) {
			totalNumberOfValues += bins.get(i);
		}
		return totalNumberOfValues;
	}

	protected void incrementBin(int binIndex) {
		incrementBin(binIndex, 1);
	}

	public void incrementBin(int binIndex, long amount) {
		bins.set(binIndex, bins.get(binIndex) + amount);
	}

	protected int calcBin(double value) {
		return (int) ((value - lowerBound) / delta);
	}

}
